/*
 *  (c) 2005-2009 Richard Cowin (http://openrico.org)
 *  (c) 2005-2009 Matt Brown (http://dowdybrown.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
 *  file except in compliance with the License. You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the
 *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 *  either express or implied. See the License for the specific language governing permissions
 *  and limitations under the License.
 */

//  Inspired by code originally written by Tan Ling Wee on 2 Dec 2001
//  Requires prototype.js and ricoCommon.js

Rico.CalendarControl = function(id,options) {
  this.initialize(id,options);
};

Rico.CalendarControl.prototype = {
/**
 * @class Implements a pop-up Gregorian calendar.
 * Dates of adoption of the Gregorian calendar vary by country - accurate as a US & British calendar from 14 Sept 1752 to present.
 * Mark special dates with calls to addHoliday()
 * @extends Rico.Popup
 * @constructs
 * @param id unique identifier
 * @param options object may contain any of the following:<dl>
 *   <dt>startAt       </dt><dd> week starts with 0=sunday, 1=monday? default=0</dd>
 *   <dt>showWeekNumber</dt><dd> show week number in first column? default=0</dd>
 *   <dt>showToday     </dt><dd> show "Today is..." in footer? default=1</dd>
 *   <dt>repeatInterval</dt><dd> when left/right arrow is pressed, repeat action every x milliseconds, default=100</dd>
 *   <dt>dateFmt       </dt><dd> date format for return value (one of values accepted by {@link Date#formatDate}), default=ISO8601</dd>
 *   <dt>minDate       </dt><dd> earliest selectable date? default=today-50 years</dd>
 *   <dt>maxDate       </dt><dd> last selectable date? default=today+50 years</dd>
 *</dl>
 */
  initialize: function(id,options) {
    this.id=id;
    var today=new Date();
    Rico.extend(this, new Rico.Popup());
    Rico.extend(this.options, {
      ignoreClicks:true,
      startAt : 0,
      showWeekNumber : 0,
      showToday : 1,
      repeatInterval : 100,
      dateFmt : 'ISO8601',
      minDate : new Date(today.getFullYear()-50,0,1),
      maxDate : new Date(today.getFullYear()+50,11,31)
    });
    Rico.extend(this.options, options || {});
    /**
     * alias for closePopup
     * @function
     */
    this.close=this.closePopup;
    this.bPageLoaded=false;
    this.img=[];
    this.Holidays={};
    this.weekString=Rico.getPhraseById("calWeekHdg");
    this.re=/^\s*(\w+)(\W)(\w+)(\W)(\w+)/i;
    this.setDateFmt(this.options.dateFmt);
  },


  setDateFmt: function(fmt) {
    this.dateFmt=(fmt=='rico') ? Rico.dateFmt : fmt;
    Rico.log(this.id+' date format set to '+this.dateFmt);
    this.dateParts={};
    if (this.re.exec(this.dateFmt)) {
      this.dateParts[RegExp.$1]=0;
      this.dateParts[RegExp.$3]=1;
      this.dateParts[RegExp.$5]=2;
    }
  },
  
/**
 * Call before displaying calendar to highlight special days
 * @param d day (1-31)
 * @param m month (1-12)
 * @param y year (0 implies a repeating holiday)
 * @param desc description
 * @param bgColor background color for cell displaying this day (CSS value, defaults to '#DDF')
 * @param txtColor text color for cell displaying this day (CSS value), if not specified it is displayed with the same color as other days
 */
  addHoliday : function(d, m, y, desc, bgColor, txtColor) {
    this.Holidays[this.holidayKey(y,m-1,d)]={desc:desc, txtColor:txtColor, bgColor:bgColor || '#DDF'};
  },
  
/** @private */
  holidayKey : function(y,m,d) {
    return 'h'+Rico.zFill(y,4)+Rico.zFill(m,2)+Rico.zFill(d,2);
  },

  atLoad : function() {
    Rico.log('Calendar#atLoad: '+this.id);
    this.createContainer();
    this.container.id=this.id;
    //this.container.style.width="auto";
    this.content.className=Rico.theme.calendar || 'ricoCalContainer';

    this.maintab=document.createElement("table");
    this.maintab.cellSpacing=2;
    this.maintab.cellPadding=0;
    this.maintab.border=0;
    this.maintab.style.borderCollapse='separate';
    this.maintab.className='ricoCalTab';
    if (Rico.theme.calendarTable) Rico.addClass(this.maintab,Rico.theme.calendarTable)
    this.tbody=Rico.getTBody(this.maintab);

    var r,c,d,i,j,img,dow,a,s,tab;
    this.colStart=this.options.showWeekNumber ? 1 : 0;
    for (i=0; i<7; i++) {
      r=this.tbody.insertRow(-1);
      r.className='row'+i;
      for (c=0; c<7+this.colStart; c++) {
        r.insertCell(-1);
      }
    }
    r=this.tbody.rows[0];
    r.className='ricoCalDayNames';
    if (this.options.showWeekNumber) {
      r.cells[0].innerHTML=this.weekString;
      for (i=0; i<7; i++) {
        this.tbody.rows[i].cells[0].className='ricoCalWeekNum';
      }
    }
    this.styles=[];
    for (i=0; i<7; i++) {
      dow=(i+this.options.startAt) % 7;
      r.cells[i+this.colStart].innerHTML=Rico.dayAbbr(dow);
      this.styles[i]='ricoCal'+dow;
    }
    
    // table header (navigation controls)
    this.thead=this.maintab.createTHead();
    r=this.thead.insertRow(-1);
    c=r.appendChild(document.createElement("th"));
    c.colSpan=7+this.colStart;
    d=c.appendChild(document.createElement("div"));
    //d.style.padding='3px';
    d.className=Rico.theme.calendarHeading || 'RicoCalHeading';
    
    d.appendChild(this._createTitleSection('Month'));
    d.appendChild(this._createTitleSection('Year'));
    new Rico.HoverSet(d.getElementsByTagName('a'));
    new Rico.HoverSet(this.tbody.getElementsByTagName('td'),{ hoverNodes: function(e) { return e.innerHTML.match(/^\d+$/) ? [e] : []; } });
    d.appendChild(Rico.closeButton(Rico.eventHandle(this,'close')));

    // table footer (today)
    if (this.options.showToday) {
      this.tfoot=this.maintab.createTFoot();
      r=this.tfoot.insertRow(-1);
      this.todayCell=r.insertCell(-1);
      this.todayCell.colSpan=7+this.colStart;
      if (Rico.theme.calendarFooter) Rico.addClass(this.todayCell,Rico.theme.calendarFooter);
      Rico.eventBind(this.todayCell,"click", Rico.eventHandle(this,'selectNow'), false);
    }
    this.content.appendChild(this.maintab);
    var ie6=Rico.isIE && Rico.ieVersion < 7;
    var selectOptions={shadow: !ie6};
    
    // month selector
    this.monthPopup=new Rico.Popup(document.createElement("div"),selectOptions);
    this.monthPopup.closePopup();
    tab=document.createElement("table");
    tab.className='ricoCalMenu';
    if (Rico.theme.calendarPopdown) Rico.addClass(tab,Rico.theme.calendarPopdown);
    tab.cellPadding=2;
    tab.cellSpacing=0;
    tab.border=0;
    tab.style.borderCollapse='separate';
    tab.style.margin='0px';
    for (i=0; i<4; i++) {
      r=tab.insertRow(-1);
      for (j=0; j<3; j++) {
        c=r.insertCell(-1);
        a=document.createElement("a");
        a.innerHTML=Rico.monthAbbr(i*3+j);
        a.name=i*3+j;
        if (Rico.theme.calendarDay) Rico.addClass(a,Rico.theme.calendarDay);
        c.appendChild(a);
        Rico.eventBind(a,"click", Rico.eventHandle(this,'selectMonth'), false);
      }
    }
    new Rico.HoverSet(tab.getElementsByTagName('a'));
    this.monthPopup.content.appendChild(tab);
    this.content.appendChild(this.monthPopup.container);
    
    // year selector
    this.yearPopup=new Rico.Popup(document.createElement("div"),selectOptions);
    this.yearPopup.closePopup();
    this.yearPopup.content.className='ricoCalYearPrompt';
    if (Rico.theme.calendarPopdown) Rico.addClass(this.yearPopup.content,Rico.theme.calendarPopdown);
    var tab=document.createElement("table");
    tab.cellPadding=2;
    tab.cellSpacing=0;
    tab.border=0;
    tab.style.borderCollapse='separate';
    tab.style.margin='0px';
    r=tab.insertRow(-1);
    this.yearLabel=r.insertCell(-1);
    this.yearLabel.colSpan=3;
    r=tab.insertRow(-1);
    c=r.insertCell(-1);
    this.yearInput=c.appendChild(document.createElement("input"));
    this.yearInput.maxlength=4;
    this.yearInput.size=4;
    Rico.eventBind(this.yearInput,"keypress", Rico.eventHandle(this,'yearKey'), false);
    c=r.insertCell(-1);
    c.appendChild(Rico.floatButton('Checkmark', Rico.eventHandle(this,'processPopUpYear')));
    c=r.insertCell(-1);
    c.appendChild(Rico.floatButton('Cancel', Rico.eventHandle(this,'popDownYear')));
    this.yearPopup.content.appendChild(tab);
    this.content.appendChild(this.yearPopup.container);

    //this.yearLabel.className='ricoCalYearPromptText';

    // fix anchors so they work in IE6
    a=this.content.getElementsByTagName('a');
    for (i=0; i<a.length; i++) {
      a[i].href='javascript:void(0)';
    }
    
    Rico.eventBind(this.tbody,"click", Rico.eventHandle(this,'saveAndClose'));
    this.close();
    this.bPageLoaded=true;
  },

  _createTitleSection : function(section) {
    var s=document.createElement("span");
    s.className='RicoCal'+section+'Heading';

    var a=s.appendChild(document.createElement("a"));
    a.className='Rico_leftArrow';
    if (Rico.theme.leftArrowAnchor) Rico.addClass(a,Rico.theme.leftArrowAnchor);
    a.appendChild(this.createNavArrow('dec'+section,'left'));

    a=s.appendChild(document.createElement("a"));
    a.style.display='inline';
    Rico.eventBind(a,"click", Rico.eventHandle(this,'popUp'+section), false);
    this['title'+section]=a;

    a=s.appendChild(document.createElement("a"));
    a.className='Rico_rightArrow';
    if (Rico.theme.rightArrowAnchor) Rico.addClass(a,Rico.theme.rightArrowAnchor);
    a.appendChild(this.createNavArrow('inc'+section,'right'));
    return s
  },
  
  selectNow : function() {
    var today = new Date();
    this.dateNow  = today.getDate();
    this.monthNow = today.getMonth();
    this.yearNow  = today.getFullYear();
    this.monthSelected=this.monthNow;
    this.yearSelected=this.yearNow;
    this.constructCalendar();
  },
  
/** @private */
  createNavArrow: function(funcname,gifname) {
    var img;
    img=document.createElement("span");
    img.className=Rico.theme[gifname+'Arrow'] || 'Rico_'+gifname+'Arrow';
    Rico.eventBind(img,"click", Rico.eventHandle(this,funcname), false);
    Rico.eventBind(img,"mousedown", Rico.eventHandle(this,'mouseDown'), false);
    Rico.eventBind(img,"mouseup", Rico.eventHandle(this,'mouseUp'), false);
    Rico.eventBind(img,"mouseout", Rico.eventHandle(this,'mouseUp'), false);
    return img;
  },

/** @private */
  mouseDown: function(e) {
    var el=Rico.eventElement(e);
    this.repeatFunc=Rico.bind(this,el.name);
    this.timeoutID=Rico.runLater(500,this,'repeatStart');
  },
  
/** @private */
  mouseUp: function(e) {
    clearTimeout(this.timeoutID);
    clearInterval(this.intervalID);
  },
  
/** @private */
  repeatStart : function() {
    clearInterval(this.intervalID);
    this.intervalID=setInterval(this.repeatFunc,this.options.repeatInterval);
  },
  
/**
 * @returns true if yr/mo is within minDate/MaxDate
 */
  isValidMonth : function(yr,mo) {
    if (yr < this.options.minDate.getFullYear()) return false;
    if (yr == this.options.minDate.getFullYear() && mo < this.options.minDate.getMonth()) return false;
    if (yr > this.options.maxDate.getFullYear()) return false;
    if (yr == this.options.maxDate.getFullYear() && mo > this.options.maxDate.getMonth()) return false;
    return true;
  },

  incMonth : function() {
    var newMonth=this.monthSelected+1;
    var newYear=this.yearSelected;
    if (newMonth>11) {
      newMonth=0;
      newYear++;
    }
    if (!this.isValidMonth(newYear,newMonth)) return;
    this.monthSelected=newMonth;
    this.yearSelected=newYear;
    this.constructCalendar();
  },

  decMonth : function() {
    var newMonth=this.monthSelected-1;
    var newYear=this.yearSelected;
    if (newMonth<0) {
      newMonth=11;
      newYear--;
    }
    if (!this.isValidMonth(newYear,newMonth)) return;
    this.monthSelected=newMonth;
    this.yearSelected=newYear;
    this.constructCalendar();
  },
  
/** @private */
  selectMonth : function(e) {
    var el=Rico.eventElement(e);
    this.monthSelected=parseInt(el.name,10);
    this.constructCalendar();
    Rico.eventStop(e);
  },

  popUpMonth : function() {
    if (this.monthPopup.visible()) {
      this.popDownMonth();
      return;
    }
    this.popDownYear();
    this.monthPopup.openPopup(this.titleMonth.parentNode.offsetLeft, this.thead.offsetHeight+2);
  },

  popDownMonth : function() {
    this.monthPopup.closePopup();
  },

  popDownYear : function() {
    this.yearPopup.closePopup();
    this.yearInput.disabled=true;  // make sure this does not get submitted
  },

/**
 * Prompt for year
 */
  popUpYear : function() {
    if (this.yearPopup.visible()) {
      this.popDownYear();
      return;
    }
    this.popDownMonth();
    this.yearPopup.openPopup(90, this.thead.offsetHeight+2);
    this.yearLabel.innerHTML=Rico.getPhraseById("calYearRange",this.options.minDate.getFullYear(),this.options.maxDate.getFullYear());
    this.yearInput.disabled=false;
    this.yearInput.value='';   // this.yearSelected
    this.yearInput.focus();
  },
  
  yearKey : function(e) {
    switch (Rico.eventKey(e)) {
      case 27: this.popDownYear(); Rico.eventStop(e); return false;
      case 13: this.processPopUpYear(); Rico.eventStop(e); return false;
    }
    return true;
  },
  
  processPopUpYear : function() {
    var newYear=this.yearInput.value;
    newYear=parseInt(newYear,10);
    if (isNaN(newYear) || newYear<this.options.minDate.getFullYear() || newYear>this.options.maxDate.getFullYear()) {
      alert(Rico.getPhraseById("calInvalidYear"));
    } else {
      this.yearSelected=newYear;
      this.popDownYear();
      this.constructCalendar();
    }
  },
  
  incYear : function() {
    if (this.yearSelected>=this.options.maxDate.getFullYear()) return;
    this.yearSelected++;
    this.constructCalendar();
  },

  decYear : function() {
    if (this.yearSelected<=this.options.minDate.getFullYear()) return;
    this.yearSelected--;
    this.constructCalendar();
  },

  // tried a number of different week number functions posted on the net
  // this is the only one that produced consistent results when comparing week numbers for December and the following January
  WeekNbr : function(year,month,day) {
    var when = new Date(year,month,day);
    var newYear = new Date(year,0,1);
    var offset = 7 + 1 - newYear.getDay();
    if (offset == 8) offset = 1;
    var daynum = ((Date.UTC(year,when.getMonth(),when.getDate(),0,0,0) - Date.UTC(year,0,1,0,0,0)) /1000/60/60/24) + 1;
    var weeknum = Math.floor((daynum-offset+7)/7);
    if (weeknum == 0) {
      year--;
      var prevNewYear = new Date(year,0,1);
      var prevOffset = 7 + 1 - prevNewYear.getDay();
      weeknum = (prevOffset == 2 || prevOffset == 8) ? 53 : 52;
    }
    return weeknum;
  },

  constructCalendar : function() {
    var aNumDays = [31,0,31,30,31,30,31,31,30,31,30,31];
    var startDate = new Date (this.yearSelected,this.monthSelected,1);
    var endDate,numDaysInMonth,i,colnum;

    if (typeof this.monthSelected!='number' || this.monthSelected>=12 || this.monthSelected<0) {
      alert('ERROR in calendar: monthSelected='+this.monthSelected);
      return;
    }

    if (this.monthSelected==1) {
      endDate = new Date (this.yearSelected,this.monthSelected+1,1);
      endDate = new Date (endDate - (24*60*60*1000));
      numDaysInMonth = endDate.getDate();
    } else {
      numDaysInMonth = aNumDays[this.monthSelected];
    }
    var dayPointer = startDate.getDay() - this.options.startAt;
    if (dayPointer<0) dayPointer+=7;
    this.popDownMonth();
    this.popDownYear();

    //this.bgcolor=Rico.getStyle(this.tbody,'background-color');
    //this.bgcolor=this.bgcolor.replace(/\"/g,'');
    if (this.options.showWeekNumber) {
      for (i=1; i<7; i++) {
        this.tbody.rows[i].cells[0].innerHTML='&nbsp;';
      }
    }
    for ( i=0; i<dayPointer; i++ ) {
      this.resetCell(this.tbody.rows[1].cells[i+this.colStart]);
    }

    for ( var datePointer=1,r=1; datePointer<=numDaysInMonth; datePointer++,dayPointer++ ) {
      colnum=dayPointer % 7;
      if (this.options.showWeekNumber && colnum==0) {
        this.tbody.rows[r].cells[0].innerHTML=this.WeekNbr(this.yearSelected,this.monthSelected,datePointer);
      }
      var c=this.tbody.rows[r].cells[colnum+this.colStart];
      c.innerHTML=datePointer;
      c.className=this.styles[colnum];
      if ((datePointer==this.dateNow)&&(this.monthSelected==this.monthNow)&&(this.yearSelected==this.yearNow)) {
        Rico.addClass(c,Rico.theme.calendarToday || 'ricoCalToday');
      }
      if (Rico.theme.calendarDay) Rico.addClass(c,Rico.theme.calendarDay);
      if ((datePointer==this.odateSelected) && (this.monthSelected==this.omonthSelected) && (this.yearSelected==this.oyearSelected)) {
        Rico.addClass(c,Rico.theme.calendarSelectedDay || 'ricoSelectedDay');
      }
      var h=this.Holidays[this.holidayKey(this.yearSelected,this.monthSelected,datePointer)];
      if (!h)  {
        h=this.Holidays[this.holidayKey(0,this.monthSelected,datePointer)];
      }
      c.style.color=h ? h.txtColor : '';
      c.style.backgroundColor=h ? h.bgColor : '';
      c.title=h ? h.desc : '';
      if (colnum==6) r++;
    }
    while (dayPointer<42) {
      colnum=dayPointer % 7;
      this.resetCell(this.tbody.rows[r].cells[colnum+this.colStart]);
      dayPointer++;
      if (colnum==6) r++;
    }

    this.titleMonth.innerHTML = Rico.monthAbbr(this.monthSelected);
    this.titleYear.innerHTML = this.yearSelected;
    if (this.todayCell) {
      this.todayCell.innerHTML = Rico.getPhraseById("calToday",this.dateNow,Rico.monthAbbr(this.monthNow),this.yearNow,this.monthNow+1);
    }
  },
  
/** @private */
  resetCell: function(c) {
    c.innerHTML="&nbsp;";
    c.className='ricoCalEmpty';
    c.style.color='';
    c.style.backgroundColor='';
    c.title='';
  },
  
/** @private */
  saveAndClose : function(e) {
    Rico.eventStop(e);
    var el=Rico.eventElement(e);
    var s=el.innerHTML.replace(/&nbsp;/g,'');
    if (s=='' || el.className=='ricoCalWeekNum') return;
    var day=parseInt(s,10);
    if (isNaN(day)) return;
    var d=new Date(this.yearSelected,this.monthSelected,day);
    var dateStr=Rico.formatDate(d,this.dateFmt=='ISO8601' ? 'yyyy-mm-dd' : this.dateFmt);
    if (this.returnValue) {
      this.returnValue(dateStr);
      this.close();
    }
  },

  open : function(curval) {
    if (!this.bPageLoaded) return;
    var today = new Date();
    this.dateNow  = today.getDate();
    this.monthNow = today.getMonth();
    this.yearNow  = today.getFullYear();
    this.oyearSelected = -1;
    if (typeof curval=='object') {
      this.odateSelected  = curval.getDate();
      this.omonthSelected = curval.getMonth();
      this.oyearSelected  = curval.getFullYear();
    } else if (this.dateFmt=='ISO8601') {
      var d=Rico.setISO8601(curval);
      if (d) {
        this.odateSelected  = d.getDate();
        this.omonthSelected = d.getMonth();
        this.oyearSelected  = d.getFullYear();
      }
    } else if (this.re.exec(curval)) {
      var aDate = [ RegExp.$1, RegExp.$3, RegExp.$5 ];
      this.odateSelected  = parseInt(aDate[this.dateParts.dd], 10);
      this.omonthSelected = parseInt(aDate[this.dateParts.mm], 10) - 1;
      this.oyearSelected  = parseInt(aDate[this.dateParts.yyyy], 10);
      if (this.oyearSelected < 100) {
        // apply a century to 2-digit years
        this.oyearSelected+=this.yearNow - (this.yearNow % 100);
        var maxyr=this.options.maxDate.getFullYear();
        while (this.oyearSelected > maxyr) this.oyearSelected-=100;
      }
    } else {
      if (curval) {
        alert('ERROR: invalid date passed to calendar ('+curval+')');
      }
    }
    if (this.oyearSelected > 0) {
      this.dateSelected=this.odateSelected;
      this.monthSelected=this.omonthSelected;
      this.yearSelected=this.oyearSelected;
    } else {
      this.dateSelected=this.dateNow;
      this.monthSelected=this.monthNow;
      this.yearSelected=this.yearNow;
    }
    this.constructCalendar();
    this.openPopup();
  }
};

Rico.includeLoaded('ricoCalendar.js');
